package com.suse.warmstill.aspect.powercheck;

import com.alibaba.fastjson.JSON;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.suse.warmstill.common.enums.RedisEnum;
import com.suse.warmstill.common.enums.ResponseEnum;
import com.suse.warmstill.common.exception.BusinessException;
import com.suse.warmstill.common.utils.UserIdThreadLocal;
import com.suse.warmstill.entity.TbMenu;
import com.suse.warmstill.mapper.TbMenuMapper;
import com.suse.warmstill.service.RoleMenuService;
import com.suse.warmstill.service.UserROleService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;

/**
 * @author 黄靖
 * @author 2022-10-26 16:31
 * description: 实现权限校验
 **/
@Component
@Aspect //切面 定义了通知和切点的关系
@Slf4j
public class RoleCheckAspect {


    @Autowired
    private UserROleService userROleService;

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private TbMenuMapper menuMapper;

    @Autowired
    private RoleMenuService roleMenuService;


    @Pointcut("@annotation(com.suse.warmstill.aspect.powercheck.RoleCheck)")
    public void checkMenu() {
    }

    //前置通知
    @Before("checkMenu() && @annotation(roleCheck)")
    public void checkRole(JoinPoint joinPoint, RoleCheck roleCheck) throws Throwable {
        // UserIdThreadLocal.put(111L);
        /*
         * 1、从local 里面拿用户的 id
         * */
        final Long userId = UserIdThreadLocal.get();

        /*
         * 拿到 user 对应的 角色 集合
         * */
        final List<Long> roleIds = userROleService.getRoleIdByUserId(userId);
        /*
         * 从 redis中 拿到所有的 角色 对应的 menu 集合
         * */

        Set menus = getMenuListByRoleId(roleIds);
        /*
         * 拿到对应的 menu菜单的path
         * */
        final String path = roleCheck.path();

        menus.retainAll(Collections.singleton(path));

        if (menus.size() == 0) {
            throw new BusinessException(ResponseEnum.USER_NOAUTHO.getCode(), ResponseEnum.USER_NOAUTHO.getMsg());
        }

    }


    private Set getMenuListByRoleId(List<Long> roleIds) {
        //先从 redis中查看是否有 roleId+menus
        Set<String> menus = new HashSet<>();
        roleIds.stream().forEach((roleId) -> {
            String redisKey = RedisEnum.RoleId_MENUS + roleId;
            String menuStr = redisTemplate.opsForValue().get(redisKey);

            // 如果redis中不存在,从redis中查，并且 存储redis
            if (StringUtils.isBlank(menuStr)) {
                synchronized (RoleCheck.class) {
                    if (StringUtils.isBlank(redisTemplate.opsForValue().get(redisKey))) {

                        final List<Long> menuIds = roleMenuService.listMenuByRoleId(roleId);

                        final List<String> menusList = menuMapper.selectBatchIds(menuIds).stream().map(TbMenu::getPath).collect(Collectors.toList());

                        /*
                         * 异步存储redis
                         * */
                        CompletableFuture.runAsync(() -> {
                            redisTemplate.opsForValue().set(redisKey, JSON.toJSONString(menusList));
                        });

                        final Set<String> pathSet = menusList.stream().collect(Collectors.toSet());
                        menus.addAll(pathSet);

                    } else {
                        menuStr = redisTemplate.opsForValue().get(redisKey);
                    }
                }
            }

            //如果 redis中 存在
            final Set<String> collect = JSON.parseArray(menuStr, String.class).stream().collect(Collectors.toSet());
            menus.addAll(collect);

        });

        return menus;
    }


    /*  * 方法结束后 删除 threadLocal的 id
     * */
    @After(" checkMenu()")
    public void deleteThreadLocal() {
        UserIdThreadLocal.remove();
    }


}
